/*
 *  cpu_driver/main.c
 *  
 *              CPU Monitor 
 *
 *    Spring Semester.  044800.    Technion.
 *   Copyright (C) 2006  Evgeny Shulman  and Elizabeth Shtrom 
 */

#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif


#include <linux/module.h>
#include <linux/config.h>

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/smp.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/smp_lock.h>
#include <linux/device.h>
#include <linux/cpu.h>
#include <linux/notifier.h>

#include <asm/processor.h>
#include <asm/msr.h>
#include <asm/uaccess.h>
#include <asm/system.h>


#include "../cpu_monitor.h"
#include "../cpu_consts.h"
#include "../cpu_common.h"
 
#include "interrupts_monitor.h" 
#include "regs.h"

int cpu_monitor_major = CPU_MONITOR_MAJOR ;
int last_data_pid =0;
static struct class *cpu_monitor_class;

typedef struct command {
 int cpu;
 void (*func) ( void* );
 void *data;
 int   data_size;
 int   data_clean;
} command_s;




#ifdef CONFIG_SMP

static void our_smp_call(void *cmd_block)
{
 command_s *cmd = ( command_s *)cmd_block;

 if (cmd->cpu == smp_processor_id())
 	last_data_pid = current->pid;
 	( *(cmd->func) ) (cmd->data);
}


static inline void do_my_cmd(command_s *cmd  )
{
	preempt_disable();
	if (cmd->cpu == smp_processor_id()) {
		last_data_pid = current->pid;
		( *(cmd->func) ) (cmd->data);
	} else {
		smp_call_function( our_smp_call ,   cmd ,  1, 1);
	}
	preempt_enable();
}
#else				/* ! CONFIG_SMP */

static inline void do_my_cmd(command_s *cmd  )
{
	last_data_pid = current->pid;
	( *(cmd->func) ) (cmd->data);
}

#endif				/* ! CONFIG_SMP */


	


static int cpu_monitor_open(struct inode *inode, struct file *file)
{
	unsigned int cpu = iminor(file->f_dentry->d_inode);
	struct cpuinfo_x86 *c = &(cpu_data)[cpu];
	PDEBUG( "cpu_monitor_open: checking  device for %d cpu ..\n", cpu);
	
	if (cpu >= NR_CPUS || !cpu_online(cpu))
		return -ENXIO;	/* No such CPU */
	if (c->cpuid_level < 0)
		return -EIO;	/* CPUID not supported */
	reset_interrupts_monitor( cpu);
	
	return 0;
}

static int cpu_monitor_release(struct inode *inode, struct file *file)
{
	stop_interrupts_monitor();
	return 0;
}

static int cpu_monitor_ioctl(struct inode *inode, struct file *file, unsigned int cmd , unsigned long __arg){
 
	int              err = 0;
	command_s        my_cmd;
	regs_s           reg_data;
	cpuid_s          cpuid_reg_data;
	im_irq_stat_s    im_irq_stat_data;
	int is_im;	
	int              index=0;
	int              count=USER_ENTRIES;
//	unsigned long    *pgd_table;
	unsigned long    *pt_table;
	
	cm_desc_t        gdt_table[GDT_ENTRIES];
	cm_desc_t        ldt_table[LDT_ENTRIES];
	cm_gate_desc_t   idt_table[IDT_ENTRIES];
	cm_tss_struct    tss_table;
	
	pgd_indirect pgd ;
	
	void __user *arg = (void __user *) __arg;
	int pid[2]; 

	PDEBUG ("cpu_monitor_ioctl: PID = %d   \n",current->pid);
 	PDEBUG( "cpu_monitor_ioctl: checking..\n");
	
  if (_IOC_TYPE(cmd) != CPU_MONITOR_IOC_MAGIC){
  	  PDEBUG( "cpu_monitor_ioctl: CPU_MONITOR_IOC_MAGIC not equal to cmd\n") ;
  	  return -ENOTTY;
  }
	if (_IOC_NR(cmd) >CPU_MONITOR_IOC_MAXNR)    {
		PDEBUG( "cpu_monitor_ioctl: CPU_MONITOR_IOC_MAGIC less than cmd\n");
		return -ENOTTY;
	}
  
  if (_IOC_DIR(cmd) & _IOC_READ)
	{
		 err = !access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd));
		 if (err) {
		 	PDEBUG( "cpu_monitor_ioctl:memory not VERIFY_WRITE  err= %d\n", err);
			return -EFAULT;
		}
	}else if (_IOC_DIR(cmd) & _IOC_WRITE) {
		err =  !access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd));
		if (err){
			PDEBUG( "cpu_monitor_ioctl:memory not VERIFY_READ err= %d\n", err);
			return -EFAULT;
  	}
  }
  
  my_cmd.cpu = iminor(file->f_dentry->d_inode);
  
	my_cmd.func = NULL;
	my_cmd.data = NULL;
	my_cmd.data_size  = 0;
	my_cmd.data_clean = 1;
  switch (cmd){
	  case CPU_MONITOR_IOC_GET_REGS:
	  		PDEBUG( "cpu_monitor_ioctl: %d cpu get regs\n",  my_cmd.cpu );
			my_cmd.func = &get_regs;
	  		my_cmd.data = (void * ) &reg_data;
	  		my_cmd.data_size =       sizeof( regs_s );
				break;
				
		case CPU_MONITOR_IOC_GET_GDT:
	  		my_cmd.func=  &get_gdt_table;
	  		my_cmd.data = (void * )gdt_table;
	  		my_cmd.data_size= GDT_SIZE;
	  		break;
				
    case CPU_MONITOR_IOC_GET_LDT:
    	  err = get_ldt_table( (void *)ldt_table ); 
    	  if ( err )
    	  	return err;
				if( copy_to_user( arg, ldt_table , LDT_SIZE ) )
					return -EFAULT;
					
				PDEBUG( "cpu_monitor_ioctl: copy of  %d bytes is done\n", LDT_SIZE);
				PDEBUG( "cpu_monitor_ioctl: done!!!\n");
				
				return 0;
	  	break;
	
	  case CPU_MONITOR_IOC_GET_IDT:
	  		my_cmd.func=&get_idt_table;
	  		my_cmd.data = (void * )idt_table;
	  		my_cmd.data_size= IDT_SIZE;
	  		break;
	  		
	  case CPU_MONITOR_IOC_GET_TSS:
	  		my_cmd.func=&get_tss_table;
	  		my_cmd.data = (void * ) &tss_table;
	  		my_cmd.data_size = TSS_SIZE;
	  		break;
	  
	case CPU_MONITOR_IOC_GET_PGD:		
				index = (( pgd_p )arg)->index;
				if ( ! CHECK_INDEX(index, PGD_TABLE_ENTRIES) ) 	 return -INDEX_INVALID;
				FIX_COUNTER( index, count, USER_ENTRIES, PGD_TABLE_ENTRIES);
				PDEBUG("cpu_monitor_ioctl:PGD:index=%d, count=%d\n", index, count );
			//	get_pgd_table( &pgd_table );

				my_cmd.data = (void * ) &pgd;
	  		    my_cmd.data_size = USER_SIZE(count);	
				my_cmd.func= &get_pgd_table;
				my_cmd.data_clean = 0;
				arg =(void __user *)(( pgd_p )arg)->pgd_table; 
				break;
				
	case CPU_MONITOR_IOC_GET_PT:
	  		index= (( pt_p )arg)->index;
				if ( ! CHECK_INDEX(index,PT_TABLE_ENTRIES) ) 	 return -INDEX_INVALID;
				FIX_COUNTER( index, count, USER_ENTRIES, PT_TABLE_ENTRIES);

				err = get_pt_table( (( pt_p )arg)->pde , &pt_table );
				if (err){
	  			PDEBUG("CPU_MONITOR_IOC_GET_PT err = %d \n" , err);
					return err;	
				}
				my_cmd.data = (void * ) ( pt_table+ index );
				my_cmd.data_size = USER_SIZE(count);	
				my_cmd.func=     NULL;
				my_cmd.data_clean = 0;
				arg =(void __user *)(( pt_p )arg)->pt_table; 
	  		break;	
	case CPU_MONITOR_IOC_GET_CPUID:
				cpuid_reg_data.op = (( cpuid_s * )arg)->op;
				my_cmd.func = &get_cpuid;
	  		my_cmd.data = (void * ) &cpuid_reg_data;	  		
	  		my_cmd.data_size = sizeof (cpuid_s);
	  		my_cmd.data_clean = 0;
	  		break;
	case CPU_MONITOR_IOC_IM_READ:
	  	  PDEBUG( "cpu_monitor_ioctl: %d cpu read interrupt stat\n",  my_cmd.cpu );
				my_cmd.func=&read_interrupts_monitor;
	  		my_cmd.data = (void * ) &im_irq_stat_data;
	  		my_cmd.data_size = sizeof (struct im_irq_stat);
	  		break;
	 case CPU_MONITOR_IOC_IM_START:
	  	  PDEBUG( "cpu_monitor_ioctl: %d cpu start interrupt monitor\n",  my_cmd.cpu );
	  	  return start_interrupts_monitor();
				break;
	
	  case CPU_MONITOR_IOC_IM_STOP:
	  	  PDEBUG( "cpu_monitor_ioctl: %d cpu stop interrupt monitor\n",  my_cmd.cpu );
	  	  return stop_interrupts_monitor();
				break;
  
	case CPU_MONITOR_IOC_IM_IS:
	  	  PDEBUG( "cpu_monitor_ioctl: %d cpu IS interrupt monitor\n",  my_cmd.cpu );
	  	  is_im = is_interrupts_monitor();
	  	  my_cmd.data  = (void * )&is_im  ;
	  	  my_cmd.data_size  = sizeof (int);
	  	  		break;
   
    case CPU_MONITOR_IOC_IM_RESET:
	  	  PDEBUG( "cpu_monitor_ioctl: reset counters\n");
	  	  return reset_all_interrupts_monitor();
	  	 		break;
  	case CPU_MONITOR_IOC_PID:
	  		pid[0] = current->pid;
			pid[1] = last_data_pid;
		  	PDEBUG( "cpu_monitor_ioctl: read pid\n");
		  	my_cmd.data = (void * ) pid;
		  	my_cmd.data_size = 2*sizeof(int);	
			my_cmd.func=     NULL;
			my_cmd.data_clean = 0;
			break;
	  
    		
	  default : 	
	  	PDEBUG( "cpu_monitor_ioctl: no ioctl is appropriate \n");
			return -EINVAL;
  }
  PDEBUG( "cpu_monitor_ioctl: case block is done\n");
	  
  
 
	if ( my_cmd.func) {
		if ( my_cmd.data_clean &&  my_cmd.data_size)
			memset(my_cmd.data,0,my_cmd.data_size );
			
		PDEBUG( "cpu_monitor_ioctl: executing command ->start\n");
		do_my_cmd (  &my_cmd );
		PDEBUG( "cpu_monitor_ioctl: executing command ->done\n");
	}  	
	
	if ( cmd == CPU_MONITOR_IOC_GET_PGD ) {
			my_cmd.data = ( void * )( pgd.pgd_table + index ) ;  
 	//		display_pgdir( my_cmd.data , 0 , PGD_TABLE_ENTRIES );
	}   
		
	if ( my_cmd.data_size )
	{   
			
		if( copy_to_user( arg, my_cmd.data, my_cmd.data_size ) )
			return -EFAULT;
		PDEBUG( "cpu_monitor_ioctl: copy of  %d bytes is done\n", my_cmd.data_size);
	}
	


	PDEBUG( "cpu_monitor_ioctl: done!!!\n");
		
	return 0;
  
}



/* DRIVER INIT / DESTROY / PROPITIES */


static struct file_operations cpu_monitor_fops = {
  .owner = THIS_MODULE,
  .open  = cpu_monitor_open,
  .release=cpu_monitor_release, 
  .ioctl = cpu_monitor_ioctl
};




static int cpu_monitor_class_device_create(int i)
{
	int err = 0;
	struct class_device *class_err;
	PDEBUG( "cpu_monitor_class_device_create: creating  device for %d cpu\n", i);
	class_err = class_device_create(cpu_monitor_class, NULL, MKDEV(cpu_monitor_major, i), NULL, "cpu_monitor%d",i);
	if (IS_ERR(class_err))
		err = PTR_ERR(class_err);
	return err;
}





static int __init cpu_monitor_init(void)
{
	int i, err;
	int result;
	
	err = 0;
	i = 0;
	PDEBUG( "cpu_monitor_init: loading..\n");
	
	PDEBUG( "cpu_monitor_init: creating main device\n");
	result  = register_chrdev(cpu_monitor_major, "cpu_monitor", &cpu_monitor_fops) ;
	if ( !result ) {
			printk(KERN_ERR "cpu_monitor_init: -unable to get major %d for cpu_monitor, result = %d",
					cpu_monitor_major, result);
		err = -EBUSY;
		goto out;
	}
	if (	cpu_monitor_major == 0 )  cpu_monitor_major = result;  /*dynamic */
	
	PDEBUG( "cpu_monitor_init: creating cpu_monitor class\n");
	cpu_monitor_class = class_create(THIS_MODULE, "cpu_monitor");
	if ( IS_ERR(cpu_monitor_class) ) {
		err = PTR_ERR(cpu_monitor_class);
		goto out_chrdev;
	}
	for_each_online_cpu(i) {
		PDEBUG( "cpu_monitor_init: creating  device for %d cpu\n", i);
		err = cpu_monitor_class_device_create(i);
		PDEBUG( "cpu_monitor_init: finished  device for %d cpu\n", i);
		
		if (err != 0) 
			goto out_class;
		reset_all_interrupts_monitor();
	}
	PDEBUG( "cpu_monitor_init: finished.\n");
	err = 0;
	goto out;

out_class:
	i = 0;
	for_each_online_cpu(i) {
		class_device_destroy(cpu_monitor_class, MKDEV(cpu_monitor_major, i));
	}
	class_destroy(cpu_monitor_class);
out_chrdev:
	unregister_chrdev(cpu_monitor_major, "cpu_monitor");	
out:
	return err;
}



static void __exit cpu_monitor_exit(void)
{
	int cpu = 0;
	PDEBUG( "cpu_monitor_exit: stopping interrupt monitor..\n");
	stop_interrupts_monitor();
	PDEBUG( "cpu_monitor_exit: removing devices..\n");
	for_each_online_cpu(cpu)
		class_device_destroy(cpu_monitor_class, MKDEV(cpu_monitor_major, cpu));
	class_destroy(cpu_monitor_class);
	unregister_chrdev(cpu_monitor_major, "cpu_monitor");
	PDEBUG( "cpu_monitor_exit: removing devices ->done\n");
	
}

module_init(cpu_monitor_init);
module_exit(cpu_monitor_exit);

MODULE_AUTHOR("Elizabeth  and Evgeny");
MODULE_DESCRIPTION("Micro Computer cource project -> CPU monitor");
MODULE_LICENSE("GPL");







